import sys
import typing
from pathlib import Path
from typing import Dict, List, Optional, Tuple, Union

from PIL import Image
from PySide6.QtCore import QModelIndex, QPointF, Qt, Signal, Slot
from PySide6.QtGui import QColor, QFont, QPalette, QStandardItem, QStandardItemModel
from PySide6.QtWidgets import QApplication, QLabel, QMainWindow, QMessageBox, QTableView

import rcc.res
from ui.image_area import ImageArea
from ui.text_dialog import TextDialog
from ui.untitled_main import Ui_MainWindow

'''
TODO:
设置选中点指示器颜色, 加载本地图片, 加载手机截图(后台线程) - 模态对话框加载动画
图片处理, 裁剪
导出
保存窗口布局
菜单 快捷键
保存图片处理步骤
选中点不在画面中时自动滚动, point_to
'''

about_info = '''
Cat Point - Simple Image Tool

A python program using PySide6 GUI framework.
'''


# A list maping luminance value to either black(0) or white(1) with the list index as luminance.
def gen_luminance_to_binary_table(threshold=115):
    return [int(i > threshold) for i in range(256)]


class MainWindow(QMainWindow):
    POINTS_TABLE_ID_COLUMN: int = 0
    POINTS_TABLE_XY_COLUMN: int = 1
    POINTS_TABLE_COLOR_COLUMN: int = 2
    ITEM_DATA_ROLE_ID = Qt.UserRole + 1
    ITEM_DATA_ROLE_POS = Qt.UserRole + 2
    ITEM_DATA_ROLE_COLOR = Qt.UserRole + 3

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.pushButton_2.clicked.connect(self.button_notify)  # type: ignore
        self.ui.pushButton.clicked.connect(self.button_binary_convert)  # type: ignore
        # Image Area
        self.view = self.ui.imageArea
        self.img: Optional[Image.Image] = None
        self.view.mouse_pos_changed.connect(self.xy_info_update)  # type: ignore
        self.view.pix_changed.connect(self.wh_info_update)  # type: ignore
        self.view.point_placed.connect(self.point_added)  # type: ignore
        self.view.point_pointed.connect(self.point_selected)  # type: ignore
        # Status bar
        self.mouse_x_info = QLabel(" xxxx ")
        self.mouse_y_info = QLabel(" yyyy ")
        self.pic_width_info = QLabel(" wwww ")
        self.pic_height_info = QLabel(" hhhh ")
        self.ui.statusbar.addPermanentWidget(QLabel("X | Y "))
        self.ui.statusbar.addPermanentWidget(self.mouse_x_info)
        self.ui.statusbar.addPermanentWidget(self.mouse_y_info)
        self.ui.statusbar.addPermanentWidget(self.pic_width_info)
        self.ui.statusbar.addPermanentWidget(self.pic_height_info)
        # Point Table
        points = self.ui.pointTable
        points.setSelectionBehavior(QTableView.SelectRows)
        points.setEditTriggers(QTableView.NoEditTriggers)
        points.setSelectionMode(QTableView.SingleSelection)
        points_model = QStandardItemModel(points)
        points_model.setColumnCount(3)
        points_model.setHorizontalHeaderLabels(('N.o', '(X, Y)', 'Color'))
        points.setModel(points_model)
        self.points_model = points_model
        points.clicked.connect(self.points_row_clicked)  # type: ignore
        points.doubleClicked.connect(self.point_remove)  # type: ignore
        # Other
        # self._ui.pushButton_3.clicked.connect(self.rm_point_0)  # type: ignore
        self.ui.btnExportPoints.clicked.connect(self.show_export_points_string)  # type: ignore
        self.ui.btnClear.clicked.connect(self.point_clear)  # type: ignore
        self.ui.actionAbout.triggered.connect(self.show_about_dialog)  # type: ignore
        self.ui.actionAbout_Qt.triggered.connect(QApplication.aboutQt)  # type: ignore

    # display color as (H,S,V) format or #RGB.
    def point_color_format_is_hsv(self) -> bool:
        return self.ui.checkBoxHsv.checkState() == Qt.Checked

    def img_display_update(self):
        assert self.img is not None
        self.view.set_pix(self.img)

    def color_text(self, color: QColor) -> str:
        if not self.point_color_format_is_hsv():
            return color.name()
        else:
            hue = color.hueF()
            saturation = color.saturationF()
            value = color.valueF()
            return f'({hue:.2},{saturation:.2},{value:.2})'

    @Slot()  # type: ignore
    def show_about_dialog(self):
        QMessageBox.about(self, 'About', about_info)

    @Slot()  # type: ignore
    def show_export_points_string(self):
        text = self.export_points_as_string()
        dialog = TextDialog(self)
        dialog.set_text(text)
        dialog.open()

    def export_points_as_string(self) -> str:
        return str(self.export_point_list())

    def export_point_list(self) -> List[Dict[str, Union[float, int]]]:
        l = []
        row_num = self.points_model.rowCount()
        for i in range(row_num):
            p = self.points_model.item(i, MainWindow.POINTS_TABLE_XY_COLUMN).data(
                MainWindow.ITEM_DATA_ROLE_POS
            )
            p = typing.cast(QPointF, p)
            pi = p.toPoint()
            c = self.points_model.item(i, MainWindow.POINTS_TABLE_COLOR_COLUMN).data(
                MainWindow.ITEM_DATA_ROLE_COLOR
            )
            c = typing.cast(QColor, c)
            d = {
                'x': pi.x(),  # int
                'y': pi.y(),  # int
                'r': c.red(),  # int
                'g': c.green(),  # int
                'b': c.blue(),  # int
                'h': c.hueF(),  # float
                's': c.saturationF(),  # float
                'v': c.valueF(),  # float
            }
            l.append(d)
        return l

    @Slot()  # type: ignore
    def point_clear(self):
        self.view.remove_all_point()
        self.points_model.removeRows(0, self.points_model.rowCount())

    @Slot(int, QPointF, QColor)  # type: ignore
    def point_added(self, id: int, p: QPointF, color: QColor):
        id_item = QStandardItem(str(id))
        id_item.setData(id, MainWindow.ITEM_DATA_ROLE_ID)
        x = int(p.x())
        y = int(p.y())
        p_item = QStandardItem(f'({x}, {y})')
        p_item.setData(p, MainWindow.ITEM_DATA_ROLE_POS)
        color_item = QStandardItem(self.color_text(color))
        color_item.setData(color, MainWindow.ITEM_DATA_ROLE_COLOR)
        self.points_model.appendRow((id_item, p_item, color_item))

    # \return (row, id)
    def get_point_id_in_point_table(self, index: QModelIndex) -> Tuple[int, int]:
        row = index.row()
        id_item = self.points_model.item(row, MainWindow.POINTS_TABLE_ID_COLUMN)
        point_id = typing.cast(int, id_item.data(MainWindow.ITEM_DATA_ROLE_ID))
        return (row, point_id)

    def get_point_color_by_row(self, row: int) -> QColor:
        return self.points_model.item(row, MainWindow.POINTS_TABLE_COLOR_COLUMN).data(
            MainWindow.ITEM_DATA_ROLE_COLOR
        )

    @Slot(QModelIndex)  # type: ignore
    def point_remove(self, index: QModelIndex):
        row, id = self.get_point_id_in_point_table(index)
        self.points_model.removeRow(row)
        self.view.remove_point(id)

    def change_display_color(self, color: QColor):
        p = self.ui.colorLabel.palette()
        p.setColor(QPalette.Base, color)
        self.ui.colorLabel.setPalette(p)

    @Slot(QModelIndex)  # type: ignore
    def points_row_clicked(self, index: QModelIndex):
        row, id = self.get_point_id_in_point_table(index)
        self.change_display_color(self.get_point_color_by_row(row))
        self.view.point_to(id)

    @Slot(int)  # type: ignore
    def point_selected(self, id: int):
        id_str = str(id)
        item_list = self.points_model.findItems(id_str)
        assert len(item_list) == 1
        index = self.points_model.indexFromItem(item_list[0])
        row = index.row()
        c = self.get_point_color_by_row(row)
        self.change_display_color(c)
        self.ui.pointTable.selectRow(row)

    @Slot(int, int)  # type: ignore
    def wh_info_update(self, w: int, h: int):
        self.pic_width_info.setText(f'{w:^6}')
        self.pic_height_info.setText(f'{h:^6}')

    @Slot(QPointF)  # type: ignore
    def xy_info_update(self, p: QPointF):
        x = int(p.x())
        y = int(p.y())
        self.mouse_x_info.setText(f'{int(x):^6}')
        self.mouse_y_info.setText(f'{int(y):^6}')

    @Slot()  # type: ignore
    def button_notify(self):
        self.img = Image.open(str(Path.home() / 'screen.png'))
        self.img_display_update()

    @Slot()  # type: ignore
    def button_binary_convert(self):
        if self.img is None:
            return
        table = gen_luminance_to_binary_table(100)
        self.img = self.img.convert('L')
        self.img = self.img.point(table, "1")
        # self.img.show()
        self.img_display_update()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    font = QFont()
    font.setFamily('Consolas')
    font.setPointSize(10)
    QApplication.setFont(font)
    wnd = MainWindow()

    wnd.show()
    sys.exit(app.exec())
